#include "algorithm/rayTracing/rayTracingDataStructure/receiver/CudaRectangleReceiver.cuh"
#include <memory>
#define SOLAR_USE_CUDA_INTER_OP_DATA
#include "RayTracingPipeline.h"
#include <algorithm/analytical_model/algorithm/cpu/GeometryUtils.h>
#include <algorithm/analytical_model/algorithm/cpu/VectorConvert.h>
#include <algorithm/analytical_model/algorithm/cpu/heliostat/RectangleHeliostat.h>
#include <algorithm/analytical_model/algorithm/cuda/inter_op/Data.h>
#include <algorithm/rayTracing/dataLoader/CloudLoader.h>
#include <algorithm/rayTracing/dataLoader/SceneConfiguration.h>
#include <algorithm/rayTracing/dataLoader/SceneField.h>
#include <algorithm/rayTracing/dataLoader/SceneLoader.h>
#include <algorithm/rayTracing/dataLoader/SceneProcessor.h>
#include <algorithm/rayTracing/dataLoader/TaskLoader.h>
#include <algorithm/rayTracing/qmcrt_util/check_cuda.h>
#include <algorithm/rayTracing/qmcrt_util/global_function.cuh>
#include <algorithm/rayTracing/rayTracingDataStructure/heliostat/CudaHeliostat.cuh>
#include <algorithm/rayTracing/rayTracingDataStructure/receiver/CudaReceiver.cuh>
#include <algorithm/rayTracing/rayTracingPipeline/quasiMonteCarloRayTracing/QuasiMonteCarloRayTracer.h>
#include <algorithm/shared/tool/imageSaver/ImageSaver.h>
#include <algorithm/shared/tool/smoother/ImageSmoother.cuh>
#include <utils/Utils.h>
#include <utils/Format.h>

// #include <ui/window/dataVisualizationWindow/CudaTexture.cuh>

#include <cuda_runtime.h>
#include <cuda_runtime_api.h>
#include <driver_types.h>

#include <boost/gil.hpp>
#include <boost/gil/extension/io/png.hpp>
#include <boost/gil/extension/io/png/old.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/image_view.hpp>
#include <boost/gil/io/io.hpp>
#include <boost/gil/typedefs.hpp>

#include <bit>

// #include <boost/gil/extension/io/png_io.hpp>
// #include <boost/gil/extension/io/png_dynamic_io.hpp>

#include <algorithm/shared/tool/analyzeResultData/CudaResultAnalysis.cuh>
#include <algorithm/shared/tool/imageSaver/CudaGetFluxMap.cuh>

using namespace boost::gil;
using namespace solar;

ResultData::~ResultData()
{
    // std::cout << "------- ~ResultData -----\n";
    if (data_p_before_smooth != nullptr)
    {
        std::cout << "!!!!!  free data_p_before_smooth !!!!!\n";
        delete (data_p_before_smooth);
    }
    if (data_p_after_smooth != nullptr)
    {
        std::cout << "!!!!!  free data_p_after_smooth !!!!!\n";
        delete (data_p_after_smooth);
    }
}

// ResultData RayTracingPipeline::result_data_;

RayTracingPipeline::RayTracingPipeline()
{

    field_ = std::make_shared<SceneField>();
    config_ = std::make_shared<SceneConfiguration>();
    scene_loader_ = std::make_shared<SceneLoader>();
    // field_->loadField(scene_, simulation_paras_);      //
    // 从scene中获取场景中的定日镜和接收器 configu_->loadConfiguration(scene_,
    // simulation_paras_);
}

std::shared_ptr<ResultData> RayTracingPipeline::result_data_ = std::make_shared<ResultData>();

auto RayTracingPipeline::rayTracingOnly(bool use_file, std::string_view json_file_path,
                                        cudaStream_t stream_) -> std::tuple<float*, int2>
{
    /**
     * if use_file is TRUE, then load data from json_file_path utilizing SceneLoader.
     * if not, it appears that data loading is finished in UI Module, that is to say, SceneField
     * and SceneConfiguration are prepared.
     */
    if (use_file)
    {
        if (json_file_path == "")
        {
            std::cout << "ERROR::RayTracingPipeline::rayTracing::json_file_path is null.";
            exit(10);
        }
        loadDataFromJSON(json_file_path.data());
    }

    CloudLoader* cloud = CloudLoader::getInstance();
    /**
     * 对数据进行预处理，生成要存到GPU中的一些数组
     */
    SceneProcessor scene_processor(config_.get());
    scene_processor.stream_ = stream_;
    scene_processor.processScene(field_);
    int helio_num = field_->getHeliostats().size(); // 场景中定日镜的个数
    TaskLoader task_loader(helio_num);
    //  存的是子定日镜的顶点----存所有子定日镜的顶点，每个子定日镜只需要存3个顶点即可
    float3* d_subheliostat_vertexes = nullptr;
    int subheliostatNums = setFlatRectangleHeliostatVertex(d_subheliostat_vertexes,
                                                           field_->getHeliostats(), 0, helio_num);
    QuasiMonteCarloRayTracer QMCRTracer;
    QMCRTracer.stream_ = stream_;
    bool res;
    QMCRTracer.rayTracingViaField(field_, helio_num, d_subheliostat_vertexes, res, cloud);
    auto* result_data = field_->getReceivers()[0]->getDeviceImage();
    auto resolution = field_->getReceivers()[0]->resolution_;
    float* result_data_copy = nullptr;
    checkCudaErrors(cudaMallocAsync(std::addressof(result_data_copy),
                                    sizeof(float) * resolution.x * resolution.y, stream_));
    checkCudaErrors(cudaMemcpyAsync(result_data_copy, result_data,
                                    sizeof(float) * resolution.x * resolution.y,
                                    cudaMemcpyDeviceToDevice, stream_));
    cuda::ImageSmoother::image_smooth(result_data_copy, 8, 0.0, resolution.x, resolution.y, 2.0f,
                                      stream_);
    field_->getReceivers()[0]->Cclean_image_content();
    return {result_data_copy, resolution};
}

auto RayTracingPipeline::rayTracingOnlyImagePlane(bool use_file, std::string_view json_file_path,
                                                  cudaStream_t stream_) -> std::tuple<float*, int2>
{
    /**
     * if use_file is TRUE, then load data from json_file_path utilizing SceneLoader.
     * if not, it appears that data loading is finished in UI Module, that is to say, SceneField
     * and SceneConfiguration are prepared.
     */
    if (use_file)
    {
        if (json_file_path == "")
        {
            std::cout << "ERROR::RayTracingPipeline::rayTracing::json_file_path is null.";
            exit(10);
        }
        loadDataFromJSON(json_file_path.data());
    }

    CloudLoader* cloud = CloudLoader::getInstance();
    /**
     * 对数据进行预处理，生成要存到GPU中的一些数组
     */
    SceneProcessor scene_processor(config_.get());
    scene_processor.stream_ = stream_;
    scene_processor.processScene(field_);

    // 旋转矩形接收器，使其x轴与成像平面的x轴对齐

    auto current_receiver = std::dynamic_pointer_cast<cuda::CudaRectangleReceiver>(field_->getReceivers()[0]);
    auto current_heliostat = field_->getHeliostats()[0];
    auto heliostat_size = current_heliostat->size_;
    auto heliostat_normal = current_heliostat->normal_;
    const auto& [image_plane_x_axis, image_plane_y_axis, image_plane_z_axis] = cpu::getImagePlaneAxis(
        structCopy<Float3>(current_receiver->getFocusCenter(current_heliostat->getPosition())),
        structCopy<Float3>(current_heliostat->getPosition()),
        std::bit_cast<Float3*>(std::addressof(current_heliostat->vertex_)));
    current_receiver->asImagePlane(structCopy<float3>(image_plane_x_axis));

    int helio_num = field_->getHeliostats().size(); // 场景中定日镜的个数
    TaskLoader task_loader(helio_num);
    //  存的是子定日镜的顶点----存所有子定日镜的顶点，每个子定日镜只需要存3个顶点即可
    float3* d_subheliostat_vertexes = nullptr;
    int subheliostatNums = setFlatRectangleHeliostatVertex(d_subheliostat_vertexes,
                                                           field_->getHeliostats(), 0, helio_num);
    QuasiMonteCarloRayTracer QMCRTracer;
    QMCRTracer.stream_ = stream_;
    bool res;
    QMCRTracer.rayTracingViaField(field_, helio_num, d_subheliostat_vertexes, res, cloud);
    auto* result_data = field_->getReceivers()[0]->getDeviceImage();
    auto resolution = field_->getReceivers()[0]->resolution_;
    float* result_data_copy = nullptr;
    checkCudaErrors(cudaMallocAsync(std::addressof(result_data_copy),
                                    sizeof(float) * resolution.x * resolution.y, stream_));
    checkCudaErrors(cudaMemcpyAsync(result_data_copy, result_data,
                                    sizeof(float) * resolution.x * resolution.y,
                                    cudaMemcpyDeviceToDevice, stream_));
    cuda::ImageSmoother::image_smooth(result_data_copy, 8, 0.0, resolution.x, resolution.y, 2.0f,
                                      stream_);
    field_->getReceivers()[0]->Cclean_image_content();
    return {result_data_copy, resolution};
}

void RayTracingPipeline::rayTracing(bool use_file, std::string_view json_file_path)
{
    /**
     * if use_file is TRUE, then load data from json_file_path utilizing SceneLoader.
     * if not, it appears that data loading is finished in UI Module, that is to say, SceneField
     * and SceneConfiguration are prepared.
     */
    if (use_file)
    {
        if (json_file_path == "")
        {
            std::cout << "ERROR::RayTracingPipeline::rayTracing::json_file_path is null.";
            exit(10);
        }
        loadDataFromJSON(json_file_path.data());
    }

    // --------------- 云层的加载 ---------------------
    //

    std::cout << "RayTracingPipeline::rayTracing()::begin load cloud" << std::endl;
    CloudLoader* cloud = CloudLoader::getInstance();
    // std::string cloud_filepath = scene_loader->getCloudFilePath(scene_);
    // std::string cloud_filepath =
    // "/home/liu/Git_resposity/solar/solar_software/solar_2/163230cloud.txt";
    // cloud->loadCloudMap(cloud_file_path_);      //
    // ！！！！！！！！！！！！！！！！！！！注意：：这里如果没有设置路径的话，会报错
    std::cout << "RayTracingPipeline::rayTracing()::end load cloud" << std::endl;

    /**
     * 对数据进行预处理，生成要存到GPU中的一些数组
     */
    std::cout << "RayTracingPipeline::rayTracing()::begin SceneProcessor process field"
              << std::endl;
    SceneProcessor scene_processor(config_.get());
    std::cout << "!!!" << std::endl;
    scene_processor.processScene(field_);
    std::cout << "RayTracingPipeline::rayTracing()::end SceneProcessor process field" << std::endl;
    int helio_num = field_->getHeliostats().size(); // 场景中定日镜的个数
    TaskLoader task_loader(helio_num);

    std::cout << "RayTracingPipeline::rayTracing()::begin setFlatRectangleHeliostatVertex"
              << std::endl;
    float3* d_subheliostat_vertexes =
        nullptr; //  存的是子定日镜的顶点----存所有子定日镜的顶点，每个子定日镜只需要存3个顶点即可
    int subheliostatNums = setFlatRectangleHeliostatVertex(d_subheliostat_vertexes,
                                                           field_->getHeliostats(), 0, helio_num);
    std::cout << "--------subheliostatNums = " << subheliostatNums << std::endl;

    QuasiMonteCarloRayTracer QMCRTracer;
    bool res;
    auto start_time = std::chrono::high_resolution_clock::now();
    auto end_time = start_time;
    long long elapsed;
    QMCRTracer.rayTracingViaField(field_, helio_num, d_subheliostat_vertexes, res, cloud);
    end_time = std::chrono::high_resolution_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
    std::cout << "\traytracing process took " << elapsed << " microseconds." << std::endl;
    std::cout << "========raytracing end===========" << std::endl;

    start_time = std::chrono::high_resolution_clock::now();
    result_data_->output_file_path = "outputfiles/"; // ！！！！注意末尾的反斜杠
    result_data_->receiver_pixel = config_->getReceiver_pixel_length();
    result_data_->ray_density = config_->getNum_sunshape_lights_per_group();
    result_data_->receiver_type = field_->getReceivers()[0]->type_;
    addReceiverResult(field_->getReceivers()[0]);
    end_time = std::chrono::high_resolution_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
    std::cout << "\tadd receiver result process took " << elapsed << " microseconds." << std::endl;
    std::cout << "=======FINISHED!!!==========" << std::endl;

    /**
     * TODO::use CUDA to store result into .txt file, get sum_power, maximum_flux,
     * maximum_location in kernal fuction or achieve this via multi-thread
     *
     */
    start_time = std::chrono::high_resolution_clock::now();
    analyzeResultDataViaCuda();
    end_time = std::chrono::high_resolution_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
    std::cout << "\t analyzeResultData took " << elapsed << " microseconds (use cuda)."
              << std::endl;
    std::cout << "-------------sum_power  = " << result_data_->total_power << std::endl;
    std::cout << " -------------maximum_flux = " << result_data_->maximum_flux << std::endl;
    std::cout << " -------------maximum_location = " << result_data_->maximum_location.x << ","
              << result_data_->maximum_location.y << std::endl;

    // 用cuda代替
    float sum_power;
    float maximum_flux;
    int2 maximum_location;
    start_time = std::chrono::high_resolution_clock::now();
    saveResultToTxt(sum_power, maximum_flux, maximum_location);
    end_time = std::chrono::high_resolution_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
    std::cout << "\tsave result to .txt process took " << elapsed << " microseconds." << std::endl;
    std::cout << "--------sum_power  = " << sum_power << std::endl;
    std::cout << " -------------maximum_flux = " << maximum_flux << std::endl;
    std::cout << " -------------maximum_location = " << maximum_location.x << ","
              << maximum_location.y << std::endl;
    result_data_->total_power = sum_power;
    result_data_->maximum_flux = maximum_flux;
    result_data_->maximum_location = maximum_location;

    /**
     * TODO::纹理计算部分
     */
    start_time = std::chrono::high_resolution_clock::now();
    generateFluxMapTextureViaCuda();
    end_time = std::chrono::high_resolution_clock::now();
    elapsed = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
    std::cout << "\t generateFluxMapTextureViaCuda took " << elapsed << " microseconds (use cuda)."
              << std::endl;

    plotFluxMap();
}

// 找到结果中的最大值、最大值位置、以及总能量----利用cuda
void RayTracingPipeline::analyzeResultDataViaCuda()
{
    cuda::CudaResultAnalysis cuda_result_analysis(
        result_data_->receiver_type, result_data_->receiver_pixel, result_data_->resolution);

    cuda_result_analysis.analyzeResultData(
        result_data_->device_data_after_smooth, // 注意：：这里要传设备指针
        result_data_->maximum_flux, result_data_->total_power, result_data_->maximum_location);
}

// 计算openGL所需的纹理---即fluxmap ---使用cuda
void RayTracingPipeline::generateFluxMapTextureViaCuda()
{
    // cuda_texture = std::make_shared<CudaTexture>();
    // cuda_texture->setColor();
    // cuda_texture->setCudaPara(result_data_->device_data_after_smooth,
    // result_data_->resolution.x, result_data_->resolution.y, result_data_->maximum_flux *
    // 1000); cuda_texture->generateTextureViaCudaDataPtr();
    cuda::CudaGetFluxMap cuda_get_fluxmap;
    cuda_get_fluxmap.setColor();
    cuda_get_fluxmap.calculateFluxMap(
        result_data_->device_data_after_smooth, result_data_->resolution,
        result_data_->maximum_flux * 1000 * 4.0 / 3.0, result_data_->device_fluxmap);
    // result_data_->texture_id = cuda_texture->getTextureId();
}

// 将cuda计算出的flux map 纹理保存成png格式的图片
void RayTracingPipeline::saveFluxMapToPng() {}

void RayTracingPipeline::plotFluxMap()
{
    // 用cudatexture代替

    ImageSaver::saveImg("flux_map.png", result_data_->resolution.y, result_data_->resolution.x,
                        result_data_->data_p_after_smooth,
                        result_data_->maximum_flux * 1000 * 4.0 / 3.0);
}

auto RayTracingPipeline::setFlatRectangleHeliostatVertex(
    float3*& d_heliostat_vertexs, std::vector<std::shared_ptr<cuda::CudaHeliostat>>& heliostats,
    int start_id, int end_id) -> int
{
    if (start_id < 0 || start_id > end_id || end_id > heliostats.size())
    {
        throw std::runtime_error(__FILE__ ". The index " + std::to_string(start_id) + " and " +
                                 std::to_string(end_id) + " is invalid.");
    }

    // clean up d_heliostat_vertexes
    if (d_heliostat_vertexs != nullptr)
    {
        checkCudaErrors(cudaFree(d_heliostat_vertexs));
        d_heliostat_vertexs = nullptr;
    }
    // 子定日镜的顶点----因为这里的定日镜的subhelio_row_col为(1,1)，所以这里返回的是定日镜的顶点
    std::vector<float3> sub_heliostat_vertexes;

    for (int i = start_id; i < end_id; ++i)
    {
        heliostats[i]->CGetSubHeliostatVertexes(sub_heliostat_vertexes);
    }
    // std::cout << " subheliostat_vertexes.size() = " << subHeliostatVertexes.size() <<
    // std::endl;
    auto* h_subHeliostat_vertexes = new float3[sub_heliostat_vertexes.size()];
    for (int i = 0; i < sub_heliostat_vertexes.size(); ++i)
    {
        h_subHeliostat_vertexes[i] = sub_heliostat_vertexes[i];
    }
    //    printf("subsizes=%d\n",subHeliostatVertexes.size());
    checkCudaErrors(
        cudaMalloc((void**)&d_heliostat_vertexs, sizeof(float3) * sub_heliostat_vertexes.size()));

    global_func::cpu2gpu(d_heliostat_vertexs, h_subHeliostat_vertexes,
                         sub_heliostat_vertexes.size());

    /*
    std::cout <<"-------print subheliostat vertex begin-------------\n";
    for(int i=0 ; i<subHeliostatVertexes.size() ; i++)
    {
        float3 ver = h_subHeliostat_vertexes[i];
        std::cout << "  ver = " << ver.x << "," << ver.y << "," << ver.z << std::endl;
    }
    std::cout <<"-------print subheliostat vertex end-------------\n";
    */

    delete[] h_subHeliostat_vertexes;
    h_subHeliostat_vertexes = nullptr;

    return sub_heliostat_vertexes.size();
}

void RayTracingPipeline::saveResultToTxt(float& sum_power, float& maximum_flux,
                                         int2& maximum_location)
{
    // std::cout <<"============result save to .txt begin====================" << std::endl;
    // std::cout <<"receiver_type = "<<result_data_->receiver_type<<std::endl;
    // std::cout <<"resolution = " <<result_data_->resolution.y<<","<<
    // result_data_->resolution.x<<std::endl;
    ImageSaver::saveText(result_data_->receiver_type, result_data_->output_file_path,
                         result_data_->resolution.y, result_data_->resolution.x,
                         result_data_->data_p_after_smooth, result_data_->receiver_pixel, 1.0, true,
                         sum_power, maximum_flux, maximum_location.x, maximum_location.y);
    // std::cout <<"============result save to .txt end====================" << std::endl;
}

void RayTracingPipeline::loadDataFromJSON(const std::string& json_file_path)
{
    // std::cout << "*********load data with scene_loader**************\n";

    scene_loader_->setFile(json_file_path);
    scene_loader_->readFile(config_, field_);
    // scene_loader->readFile(config_, field_);
    // field_ = scene_loader->field_2;
    // config_ = scene_loader->config_2;
    // std::cout <<"================ use data from SceneLoader field_2 + config_2\n";
}

void RayTracingPipeline::addReceiverResult(std::shared_ptr<cuda::CudaReceiver> rev)
{
    int2 resolution = rev->getResolution();
    result_data_->resolution = resolution;

    float* h_array = nullptr;
    float* d_array = rev->getDeviceImage();
    if (result_data_->data_p_before_smooth == nullptr)
    {
        global_func::gpu2cpu(result_data_->data_p_before_smooth, d_array,
                             resolution.x * resolution.y);
    }
    else
    {
        global_func::gpu2cpu(h_array, d_array, resolution.x * resolution.y);
        for (int i = 0; i < resolution.x * resolution.y; i++)
        {
            result_data_->data_p_before_smooth[i] += h_array[i];
        }
    }

    cuda::ImageSmoother::image_smooth(d_array, 8, 0.0, resolution.x, resolution.y, 2.0f);

    if (result_data_->data_p_after_smooth == nullptr)
    {
        global_func::gpu2cpu(result_data_->data_p_after_smooth, d_array,
                             resolution.x * resolution.y);
    }
    else
    {
        global_func::gpu2cpu(h_array, d_array, resolution.x * resolution.y);
        for (int i = 0; i < resolution.x * resolution.y; i++)
        {
            result_data_->data_p_after_smooth[i] += h_array[i];
        }
    }

    result_data_->device_data_after_smooth = d_array;

    delete (h_array);
    h_array = nullptr;
    d_array = nullptr;
}

void RayTracingPipeline::setConfiguration(
    std::shared_ptr<SceneConfiguration> config) // 供UI层调用，用来设置config_
{
    config_ = config;
}
void RayTracingPipeline::setSceneField(
    std::shared_ptr<SceneField> field) // 供ui层调用，用来设置field_
{
    field_ = field;
}
void RayTracingPipeline::setCloudFilePath(std::string cloud_file_path)
{
    cloud_file_path_ = cloud_file_path;
}

auto RayTracingPipeline::getConfiguration()
    -> std::shared_ptr<SceneConfiguration>& // 供UI层调用，用来获取config_
{
    return config_;
}
auto RayTracingPipeline::getSceneField()
    -> std::shared_ptr<SceneField>& // 供ui层调用，用来获取field_
{
    return field_;
}
// ResultData& RayTracingPipeline::getResultData()
// {
//     return result_data_;
// }
